package roborally.program.commands;

import be.kuleuven.cs.som.annotate.*;
import roborally.model.Robot;
import roborally.program.conditions.Condition;

/**
 * A class for dealing with loops: (while(condition)(command)).     
 * 
 * @invar	The condition of each loop must be a valid condition.
 * 			| canHaveAsCondition(getCondition())
 * @invar	The command of each loop must be a valid command.
 * 			| canHaveAsCommand(getCommand())
 * 
 * @version	1.0
 * @author  Pieter Robberechts	(1e Bach Informatica)
 * @author	Jeroen Sanders		(1e Bach Informatica)
 *
 */
public class Loop extends Command {

	/**
	 * Initialize this new loop with a line number, condition and command.
	 * 			
	 * @param 	condition
	 * 			The condition for executing the loop.
	 * @param 	command
	 * 			The command to be executed as long as the condition evaluates to true.
	 * @pre		The given condition must be a valid condition.
	 * 			| canHaveAsCondition(condition)
	 * @pre		The given command must be a valid command.
	 * 			| canHaveAsCommand(command)
	 * @post	The condition of this loop is set to the given condition.
	 * 			| new.getCondition() == condition
	 * @post	The command of this loop is set to the given command.
	 * 			| new.getCommand() == command
	 */
	public Loop(int lineNb, Condition condition, Command command) {
		super(lineNb);
		assert(canHaveAsCondition(condition));
		assert(canHaveAsCommand(command));
		this.condition = condition;
		this.command = command;
	}
	
	/**
	 * Variable registering the condition associated with this loop.
	 */
	private final Condition condition;
	
	/**
	 * Variable registering the command associated with this loop.
	 */
	private final Command command;

	/**
	 * Return the condition of this loop.
	 */
	@Basic @Raw @Immutable
	public Condition getCondition() {
		return condition;
	}

	/**
	 * Return the command of this loop.
	 */
	@Basic @Raw @Immutable
	public Command getCommand() {
		return command;
	}
	
	/**
	 * Check whether the given condition is a valid condition for a loop.
	 * 
	 * @param 	condition
	 * 			The condition to check for whether it is a valid one.
	 * @return	Return true if and only if the given condition is effective;
	 * 			else return false.
	 * 			| result == ( condition != null )
	 */
	@Raw
	public boolean canHaveAsCondition(Condition condition) {
		return ( condition != null );
	}
	
	/**
	 * Check whether the given command is a valid command for a loop.
	 * 
	 * @param 	command
	 * 			The command to check for whether it is a valid one.
	 * @return	Return true if and only if the given command is effective;
	 * 			else return false.
	 * 			| result == ( command != null )
	 */
	@Raw
	public boolean canHaveAsCommand(Command command) {
		return ( command != null );
	}

	/**
	 * Return a textual representation of this loop,
	 * formatted in accordance with its position in the program.
	 * 
	 * @return	The result is a pretty formatted string of this loop,
	 * 			taking in account his relative position in the program.
	 * 			| result == 
	 * 			|	( getIndentation(indentationLevel) + "(while\n" + 
	 *			|	condition.prettyFormat(indentationLevel+1) + "\n" + 
	 *			|	command.prettyFormat(indentationLevel+1) + 
	 *			|	getIndentation(indentationLevel) + "\n)" )
	 */
	@Override
	public String prettyFormat(int indentationLevel) {
		return getIndentation(indentationLevel) + "(while\n" + 
				condition.prettyFormat(indentationLevel+1) + "\n" + 
				command.prettyFormat(indentationLevel+1) + 
				getIndentation(indentationLevel) + "\n)";
	}

	/**
	 * Return the next basic command for a robot, given the last executed line number. 
	 * 
	 * @return	Return null if the given line number is larger than or equal to the 
	 * 			line number of this loop, or if the condition of this loop doesn't 
	 * 			evaluate to true.
	 * 			| if( lineNb >= getLineNb() || !getCondition().evaluate(robot) )
	 * 			|	then result == null
	 * @return	Search in the command of this loop for the next basic command,
	 * 			starting from the given line number, and return the result if a first 
	 * 			search in this command, starting from the given line number, doesn't return null.
	 * 			| if( getCommand().getNextBasicCommand(getCondition().getLineNb(), robot) != null )
	 * 			|	then result == getCommand().getNextBasicCommand(getCondition().getLineNb(), robot)
	 * @return	Search a second time in the command of this loop for the next basic command,
	 * 			starting from the line number of the condition, if a first search in this command, 
	 * 			starting from the given line number, returns null. (The given line number might
	 * 			be the line number of the last basic command in the loop, in this case 
	 * 			the loop should start again).
	 * 			| if( getCommand().getNextBasicCommand(lineNb, robot) == null )
	 * 			|	then result == getCommand().getNextBasicCommand(getCondition().getLineNb(), robot)
	 *
	 */
	@Override
	public BasicCommand getNextBasicCommand(int lineNb, Robot robot) {	
		// The next basic command has to be searched outside of this loop
		if(lineNb >= getLineNb() || !getCondition().evaluate(robot))
			return null;

		// The next basic command is certainly an element of this loop
		BasicCommand nextCommand = getCommand().getNextBasicCommand(lineNb, robot);
		if(nextCommand == null)
			// Start the loop again
			return getCommand().getNextBasicCommand(getCondition().getLineNb(), robot);
		return nextCommand;
	}
}
